#include <buola/cv/barcode/decode.h>
#include <numeric>

namespace buola { namespace cvi { namespace barcode {

///get the width of a bar (or sum of bars) with respect to the base bar width)
///
///\param pW the width to measure
///\param p1100BarW 700 times the base bar width
///
///\return the rounded width of \p pW in bar units
static inline int get_width(int pW,int p1100BarW)
{
    return (1100*pW+p1100BarW/2)/p1100BarW;
}

///conversion of width groups to code points
static const int8_t sWidthsToDigits[1296]=
{
    -1, -1, -1, 97, -1, -1, -1, -1, 76, -1, 87, -1, -1, -1, -1, 40, -1, 62, -1, -1, -1, -1, 78, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 57, -1, -1, -1, -1, 71, -1, 28, -1, -1, -1, -1, 37, -1, 59, -1,
    -1, -1, -1, 73, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, -1, 77, -1, -1,
    -1, -1, 22, -1, -1, -1, -1, -1, -1,106, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, 95, -1, 60, -1, -1, -1, -1, 84, -1, -1, -1, -1, -1, -1, 79, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, 54, -1, -1, -1, -1, 65, -1, 25, -1, -1, -1, -1, 34, -1, 56, -1, -1, -1, -1, 67, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,104, -1, 86, -1, -1, -1, 30,  9, 91, 88, -1, -1, -1,  1, 11, -1, -1,
    -1, -1, -1, 32, 75, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 69, -1, 27, -1, -1, -1,
    45,  6, 48, 29, -1, -1, -1, 16,  8, -1, -1, -1, -1, -1, 47, 74, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, 21, -1, -1, -1, -1, -1, 90, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 83, -1, -1, -1, -1, -1, -1, 85, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,107, -1,102, -1, -1, -1, -1, 19, -1, -1, -1, -1, -1, -1, 50, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 63, -1, 24, -1, -1, -1, 42,  3, 23, 26, -1, -1,
    -1, 13,  5, -1, -1, -1, -1, -1, 44, 68, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 39, -1, 98, -1, -1,
    -1,  0, 10, -1, -1, -1, -1, 53,  2, 41, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, 36, -1, 58, -1, -1, -1, 15, 7, -1, -1, -1, -1, 100, 17, 38, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    96, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 92, -1,101, -1, -1, -1, -1, 81, -1, -1, -1, -1,
    -1, -1, 94, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,105, -1, -1, -1, -1,
    -1, 89, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    33, -1, 55, -1, -1, -1, 12,  4, -1, -1, -1, -1, 99, 14, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1,103, -1, -1, -1, -1, -1, 31, 61, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 70, -1, -1, -1, -1, -1, 46, 72, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    80, -1, -1, -1, -1, -1, -1, 82, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, 49, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64, -1, -1, -1, -1, -1, 43, 66, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 93, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1
};

static inline int get_digit(const int *pPtr,int p1100BarW)
{
    int lA=get_width(pPtr[0]+pPtr[1],p1100BarW)-2;
    if(lA==-1) lA=0;
    if(lA<0||lA>5) return -1;
    int lB=get_width(pPtr[1]+pPtr[2],p1100BarW)-2;
    if(lB==-1) lB=0;
    if(lB<0||lB>5) return -1;
    int lC=get_width(pPtr[2]+pPtr[3],p1100BarW)-2;
    if(lC==-1) lC=0;
    if(lC<0||lC>5) return -1;
    int lD=get_width(pPtr[3]+pPtr[4],p1100BarW)-2;
    if(lD==-1) lD=0;
    if(lD<0||lD>5) return -1;

    return sWidthsToDigits[lA+6*lB+36*lC+216*lD];
}

static inline int get_reverse_digit(const int *pPtr,int p1100BarW)
{
    int lA=get_width(pPtr[4]+pPtr[5],p1100BarW)-2;
    if(lA==-1) lA=0;
    if(lA<0||lA>5) return -1;
    int lB=get_width(pPtr[3]+pPtr[4],p1100BarW)-2;
    if(lB==-1) lB=0;
    if(lB<0||lB>5) return -1;
    int lC=get_width(pPtr[2]+pPtr[3],p1100BarW)-2;
    if(lC==-1) lC=0;
    if(lC<0||lC>5) return -1;
    int lD=get_width(pPtr[1]+pPtr[2],p1100BarW)-2;
    if(lD==-1) lD=0;
    if(lD<0||lD>5) return -1;

    return sWidthsToDigits[lA+6*lB+36*lC+216*lD];
}

static inline bool validate_checksum(const int *pSyms,int pN)
{
    int lSum=pSyms[0];
    for(int i=1;i<pN-2;i++)
        lSum+=i*pSyms[i];
    return (lSum%103==pSyms[pN-2]);
}

static const char sATable[108]=
{
    ' ',    '!',    '"',    '#',    '$',    '%',    '&',    '\'',   '(',    ')',    '*',    '+',    ',',    '-',    '.',    '/',
    '0',    '1',    '2',    '3',    '4',    '5',    '6',    '7',    '8',    '9',    ':',    ';',    '<',    '=',    '>',    '?',
    '@',    'A',    'B',    'C',    'D',    'E',    'F',    'G',    'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O',
    'P',    'Q',    'R',    'S',    'T',    'U',    'V',    'W',    'X',    'Y',    'Z',    '[',    '\\',   ']',    '^',    '_',
    0,      1,      2,      3,      4,      5,      6,      7,      8,      9,      10,     11,     12,     13,     14,     15,
    16,     17,     18,     19,     20,     21,     22,     23,     24,     25,     26,     27,     28,     29,     30,     31,
    0,      0,      -1,     -5,     -4,     0,      0,      0,      0,      0,      0,      0
};

static const char sBTable[108]=
{
    ' ',    '!',    '"',    '#',    '$',    '%',    '&',    '\'',   '(',    ')',    '*',    '+',    ',',    '-',    '.',    '/',
    '0',    '1',    '2',    '3',    '4',    '5',    '6',    '7',    '8',    '9',    ':',    ';',    '<',    '=',    '>',    '?',
    '@',    'A',    'B',    'C',    'D',    'E',    'F',    'G',    'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O',
    'P',    'Q',    'R',    'S',    'T',    'U',    'V',    'W',    'X',    'Y',    'Z',    '[',    '\\',   ']',    '^',    '_',
    '`',    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'h',    'i',    'j',    'k',    'l',    'm',    'n',    'o',
    'p',    'q',    'r',    's',    't',    'u',    'v',    'w',    'x',    'y',    'z',    '{',    '|',    '}',    '~',    127,
    0,      0,      -2,     -5,     0,      -3,     0,      0,      0,      0,      0,      0
};

static inline std::string syms_to_text(const int *pSyms,int pN)
{
    std::string lText;
    lText.reserve(pN*3);
    const char *lTable;
    switch(pSyms[0])
    {
    case 103:
        lTable=sATable;
        break;
    case 104:
        lTable=sBTable;
        break;
    case 105:
    default:
        lTable=nullptr;
        break;
    }
    const char *lNextTable=lTable;
    
    for(int i=1;i<pN-2;i++)
    {
        if(lNextTable) //A or B
        {
            char lCode=lNextTable[pSyms[i]];
            if(lCode>=32)
            {
                lText+=lCode;
            }
            else if(lCode==-1)
            {
                lNextTable=sBTable;
                continue;
            }
            else if(lCode==-2)
            {
                lNextTable=sATable;
                continue;
            }
            else if(lCode==-3)
            {
                lTable=sATable;
            }
            else if(lCode==-4)
            {
                lTable=sBTable;
            }
            else if(lCode==-5)
            {
                lTable=nullptr;
            }
        }
        else
        {
            if(pSyms[i]<100)
            {
                lText+='0'+pSyms[i]/10;
                lText+='0'+pSyms[i]%10;
            }
            else if(pSyms[i]==100)
            {
                lTable=sBTable;
            }
            else if(pSyms[i]==101)
            {
                lTable=sATable;
            }
        }
        lNextTable=lTable;
    }
    
    return lText;
}

void decode_code128(const std::vector<int> &pWidths,std::vector<CSymbol> &pSymbols)
{
    int lSyms[50];
    
    const int *lBegin=&pWidths.front();
    const int *lEnd=&pWidths.back();

    for(const int *lPtr=lBegin+1;lPtr<lEnd-5;lPtr+=2)
    {
        int l1100BarW=100*(lPtr[0]+lPtr[1]+lPtr[2]+lPtr[3]+lPtr[4]+lPtr[5]);
        //check guard to the left
        if(get_width(lPtr[-1],l1100BarW)<5) continue;

        lSyms[0]=get_digit(lPtr,l1100BarW);
        if(lSyms[0]!=103&&lSyms[0]!=104&&lSyms[0]!=105&&lSyms[0]!=107) continue;

        int j=1;
        const int *lTmp;

        if(lSyms[0]==107) //it's reversed, some things are a bit different
        {
            for(lTmp=lPtr+7;lTmp<lEnd-5&&j<50;lTmp+=6)
            {
                int lTmp1100BarW=100*(lTmp[0]+lTmp[1]+lTmp[2]+lTmp[3]+lTmp[4]+lTmp[5]);
                if(lTmp1100BarW*10>l1100BarW*11||lTmp1100BarW*11<l1100BarW*10) break;
                lSyms[j]=get_reverse_digit(lTmp,lTmp1100BarW);
                if(lSyms[j]<0) break;
                j++;
                l1100BarW=lTmp1100BarW;
            }
            if(lSyms[j-1]!=103&&lSyms[j-1]!=104&&lSyms[j-1]!=105) continue;
            std::reverse(lSyms,lSyms+j);
        }
        else
        {
            for(lTmp=lPtr+6;lTmp<lEnd-6&&j<50;lTmp+=6)
            {
                int lTmp1100BarW=100*(lTmp[0]+lTmp[1]+lTmp[2]+lTmp[3]+lTmp[4]+lTmp[5]);
                if(lTmp1100BarW*10>l1100BarW*11||lTmp1100BarW*11<l1100BarW*10) break;
                lSyms[j]=get_digit(lTmp,lTmp1100BarW);
                if(lSyms[j]<0) break;
                j++;
                l1100BarW=lTmp1100BarW;
            }
            if(lSyms[j-1]!=106) continue;
            lTmp++;
        }
        
        if(!validate_checksum(lSyms,j)) continue;
        
        CSymbol lSym;
        lSym.mB=std::accumulate(lBegin,lPtr,0)>>5;
        lSym.mE=lSym.mB+(std::accumulate(lPtr,lTmp,0)>>5);
        lSym.mText=syms_to_text(lSyms,j);
        lSym.mType=EType::CODE128;
        lSym.mDirection=(lSyms[j-1]==107?-1:1);

        pSymbols.push_back(lSym);
        lPtr=lTmp+1;
    }
}
    
/*namespace barcode*/ } /*namespace cvi*/ } /*namespace buola*/ }
